//------------------------------------------------------------------------------
// <copyright file="CustomLocalFileSettingsProvider.cs" company="Damian Antonowicz">
//       copyright © 2010 Damian Antonowicz
// </copyright>
// <author>Damian Antonowicz</author>
// <email>poczta@damianantonowicz.pl</email>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Collections.Specialized;
using System.IO;
using System.Xml.Linq;

namespace DotBeer.Business.Entities
{
    public class CustomLocalFileSettingsProvider : SettingsProvider,
                                                   IApplicationSettingsProvider
    {
        #region private members

        private string localAppDataFolderPath;
        private string appSettingsFilePath;

        #endregion

        #region properties

        public override string ApplicationName
        {
            get
            {
                return EntryAssemblyHelper.ProductName;
            }
            set
            {
            }
        }

        #endregion

        #region constructors

        public CustomLocalFileSettingsProvider()
        {
            this.localAppDataFolderPath = String.Format(@"{0}\{1}\{2}",
                                                        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                                                        EntryAssemblyHelper.CompanyName,
                                                        EntryAssemblyHelper.ProductName);
            this.appSettingsFilePath = String.Format(@"{0}\{1}",
                                                     this.localAppDataFolderPath,
                                                     "user.config");
        }

        #endregion

        #region public methods

        #region IApplicationSettingsProvider members

        public SettingsPropertyValue GetPreviousVersion(SettingsContext context, SettingsProperty property)
        {
            return null;
        }

        public void Reset(SettingsContext context)
        {
            if (File.Exists(this.appSettingsFilePath))
            {
                XDocument document;

                try
                {
                    document = XDocument.Load(this.appSettingsFilePath);
                }
                catch (Exception exception)
                {
                    throw new SettingsException("Can't read settings file. Can't reset to default values. See inner exception for details.",
                                                exception);
                }

                XElement root = document.Element("settings");

                if (root == null)
                    this.ThrowExceptionDefaultDataNotFound();

                string groupName = context["GroupName"].ToString();
                XElement contextSettings = root.Element(groupName);

                if (contextSettings == null)
                    this.ThrowExceptionDefaultDataNotFound();

                foreach (XElement node in contextSettings.Elements())
                {
                    XElement defaultValue = node.Element("defaultValue");

                    if (defaultValue != null)
                    {
                        XElement value = node.Element("value");

                        if (value == null)
                        {
                            value = new XElement("value");
                            node.Add(value);
                        }

                        value.Value = defaultValue.Value;
                    }
                }

                document.Save(this.appSettingsFilePath);
            }
            else
            {
                throw new FileNotFoundException(String.Format("Settings file {0} not found. Can't reset to default values",
                                                              this.appSettingsFilePath));
            }
        }

        public void Upgrade(SettingsContext context, SettingsPropertyCollection properties)
        {
        }

        #endregion

        #region SettingsProvider members

        public override void Initialize(string name, NameValueCollection config)
        {
            base.Initialize(this.ApplicationName, config);
        }

        public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection settings)
        {
            this.CreateRequiredFoldersAndFilesIfNeeded();
            XDocument document;

            try
            {
                document = XDocument.Load(this.appSettingsFilePath);
            }
            catch
            {
                this.BackupSettingsFile();
                document = new XDocument();
                document.Add(new XElement("settings"));
            }

            XElement root = document.Element("settings");
            string groupName = context["GroupName"].ToString();
            XElement contextSettings = root.Element(groupName);

            if (contextSettings == null)
            {
                contextSettings = new XElement(groupName);
                root.Add(contextSettings);
            }

            IList<XElement> nodesToAdd = new List<XElement>();

            foreach (SettingsPropertyValue property in settings)
            {
                bool saveWithoutDefault = false;

                XElement setting = contextSettings.Elements()
                                                    .Where(element => element.Name == "setting" &&
                                                                    element.HasAttributes &&
                                                                    element.FirstAttribute.Name == "name" &&
                                                                    element.FirstAttribute.Value == property.Name &&
                                                                    element.HasElements)
                                                    .FirstOrDefault();

                if (setting != null)
                {
                    XElement defaultValue = setting.Element("defaultValue");

                    if (defaultValue != null)
                    {
                        setting = new XElement("setting", new XAttribute("name", property.Name),
                                                new XElement("value", property.SerializedValue),
                                                new XElement("defaultValue", defaultValue.Value));
                    }
                    else
                    {
                        saveWithoutDefault = true;
                    }
                }
                else
                {
                    saveWithoutDefault = true;
                }

                if (saveWithoutDefault)
                {
                    setting = new XElement("setting", new XAttribute("name", property.Name),
                                            new XElement("value", property.SerializedValue));
                }

                nodesToAdd.Add(setting);
            }

            contextSettings.RemoveAll();

            foreach (XElement node in nodesToAdd)
                contextSettings.Add(node);

            document.Save(this.appSettingsFilePath);
        }

        public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection settingsCollection)
        {
            XDocument document;

            try
            {
                document = XDocument.Load(this.appSettingsFilePath);
            }
            catch
            {
                document = new XDocument();
            }

            XElement root = document.Element("settings");

            if (root == null)
            {
                root = new XElement("settings");
                document.Add(root);
            }

            string groupName = context["GroupName"].ToString();
            XElement contextSettings = root.Element(groupName);

            if (contextSettings == null)
            {
                contextSettings = new XElement(groupName);
                root.Add(contextSettings);
            }

            SettingsPropertyValueCollection settings = new SettingsPropertyValueCollection();

            foreach (SettingsProperty property in settingsCollection)
            {
                XElement setting = contextSettings.Elements()
                                                  .Where(element => element.Name == "setting" &&
                                                                    element.HasAttributes &&
                                                                    element.FirstAttribute.Name == "name" &&
                                                                    element.FirstAttribute.Value == property.Name &&
                                                                    element.HasElements)
                                                  .FirstOrDefault();

                if (setting != null)
                {
                    XElement propertyValue = setting.Element("value");
                    settings.Add(new SettingsPropertyValue(property) { SerializedValue = propertyValue.Value });

                    XElement defaultPropertyValue = setting.Element("defaultValue");

                    if (defaultPropertyValue == null)
                    {
                        defaultPropertyValue = new XElement("defaultValue");
                        setting.Add(defaultPropertyValue);
                    }

                    defaultPropertyValue.Value = property.DefaultValue.ToString();
                }
                else
                {
                    XElement propertySetting = new XElement("setting", new XAttribute("name", property.Name),
                                                            new XElement("value", property.DefaultValue),
                                                            new XElement("defaultValue", property.DefaultValue));
                    contextSettings.Add(propertySetting);
                    settings.Add(new SettingsPropertyValue(property) { SerializedValue = property.DefaultValue });
                }
            }

            document.Save(this.appSettingsFilePath);
            return settings;
        }

        #endregion

        #endregion

        #region private methods

        private void BackupSettingsFile()
        {
            if (File.Exists(this.appSettingsFilePath))
            {
                try
                {
                    File.Copy(this.appSettingsFilePath, String.Format(@"{0}\user_{1:d_M_yyyy_HH_mm_ss}.config",
                                                                      this.localAppDataFolderPath,
                                                                      DateTime.Now));
                }
                catch
                {
                }
            }
        }

        private void CreateRequiredFoldersAndFilesIfNeeded()
        {
            this.CreateLocalAppDataFolderIfNeeded();
            this.CreateAppSettingsFileIfNeeded();
        }

        private void CreateLocalAppDataFolderIfNeeded()
        {
            if (!Directory.Exists(this.localAppDataFolderPath))
                Directory.CreateDirectory(this.localAppDataFolderPath);
        }

        private void CreateAppSettingsFileIfNeeded()
        {
            if (!File.Exists(this.appSettingsFilePath))
            {
                XElement settings = new XElement("settings");
                settings.Save(this.appSettingsFilePath);
            }
        }

        private void ThrowExceptionDefaultDataNotFound()
        {
            throw new SettingsException(String.Format("Settings file {0} doesn't contain any information about default values. Can't reset.",
                                                      this.appSettingsFilePath));
        }

        #endregion
    }
}
